<?php
/**
 * Copyright (c) 2006, PORTALIER Julien
 *
 * Licensed under The LGPL License
 * Redistributions of files must retain the above copyright notice.
 *
 * @package      FeatherCMS
 * @subpackage   Helpers
 * @copyright    Copyright (c) 2006, Julien PORTALIER
 * @link         http://julien.portalier.com
 * @license      http://www.opensource.org/licenses/lgpl-license.php The LGPL License
 */

define('WS_WIKI',  0);
define('WS_XHTML', 1);

class SyntaxHelper extends Helper
{
	protected $xhtml = '';
	protected $text  = '';
	protected $options    = array(
		'typo'  => true,
		'smile' => true
	);
	protected static $footnotes;
	public    static $imageSizes = array('square', 'thumbnail', 'small', 'medium', 'large'/*, 'original'*/);
	
	public $toc;
	
	function getXHTML($text=null, $syntax=WS_WIKI, $options=null)
	{
		if (empty($text))
			return '';
		
		if (!empty($options))
			$this->options = array_merge($this->options, $options);
		
		// syntax
		switch($syntax)
		{
			case WS_WIKI:
				$this->xhtml = '';
				self::$footnotes = array();
				$this->text = $text;
				$this->__WIKI();
				break;
			
			case WS_XHTML:
				$this->xhtml = preg_replace('/\<\?php/', '&lt;?php', $text);
				break;
			
			default:
				trigger_error(s_('Unknown syntax %d', $syntax), E_USER_ERROR);
		}
		
		// wikilinks
		$this->xhtml = preg_replace_callback('/\[\[(.+?)\]\]/', array('self', '__preg_wikilinks'), $this->xhtml);
		
		// typographer
		if ($this->options['typo'])
		{
			if (!class_exists('Typographer'))
				Loader::load('libs', 'typographer');
			$this->xhtml = Typographer::transforms($this->xhtml);
		}
		
		// smileys
		if ($this->options['smile'])
		{
			if (!class_exists('Smiler'))
				Loader::load('libs', 'smiler');
			$this->xhtml = Smiler::transforms($this->xhtml);
		}
		
		return $this->xhtml;
	}
	
	// forms
	
	function & getList()
	{
		$list = array (
			WS_WIKI  => 'Wiki Markup',
			WS_XHTML => 'HTML',
		);
		return $list;
	}
	
	// internals
	
	/** @todo Acronyms? eg: DRM(Digital Rights Management) */
	function __WIKI()
	{
		$toc_index_2 = 0;
		$toc_index_3 = 0;
		$toc_index_4 = 0;
		
		// text
		$this->text = str_replace(CRLF, LF, htmlspecialchars($this->text, ENT_NOQUOTES));
		
		// extracts groups from text
		$groups = array('');
		$index = 0;
		foreach(explode(LF, $this->text) as $line)
		{
			$test = trim($line);
			if (empty($test))
			{
				if (!empty($groups[$index]))
				{
					$index++;
					$groups[$index] = '';
				}
				continue;
			}
			$groups[$index] .= "$line\n";
		}
		
		// parsing each group to detect PARAGRAPHS, LISTS, BLOCKQUOTEs, etc.
		foreach($groups as $grp)
		{
			if (empty($grp) and strlen($grp) == 0)
				continue;
			
			// class
			preg_match('/^\.([\w\d]+): /', $grp, $match);
			if (empty($match))
				$className = null;
			else
			{
				$className = " class=\"{$match[1]}\"";
				$grp = str_replace(".{$match[1]}: ", '', $grp);
			}
			
			if (strpos($grp, '==') === 0)
			{
				// header
				preg_match('/([=]{2,})\s*(.+?)\s*[=]{2,}/', $grp, $match);
				$name = str_replace(array('[[', ']]'), '', $match[2]);
				$id   = preg_replace(array('/\s/', '/[^\d\w-_:.]/'), array('_', ''), $name);
				$h    = strlen($match[1]);
				
				$match[2] = self::__wikiParseGrp($match[2]);
				$this->xhtml .= "<h$h$className id=\"$id\"$className>{$match[2]}</h$h>";
				
				// toc
				switch ($h)
				{
					case 4:
						$toc_index_4++;
						$this->toc[$toc_index_2][$toc_index_3][$toc_index_4] = array('title' => $name, 'id' => $id);
						break;
					
					case 3:
						$toc_index_3++;
						$toc_index_4 = 0;
						$this->toc[$toc_index_2][$toc_index_3] = array('title' => $name, 'id' => $id);
						break;
					
					case 2:
						$toc_index_2++;
						$toc_index_3 = 0;
						$toc_index_4 = 0;
						$this->toc[$toc_index_2] = array('title' => $name, 'id' => $id);
				}
			}
			elseif (preg_match(':^(\*|\#)+\s+:', $grp))
			{
				// lists
				$this->xhtml .= $this->__list($grp, $grp{0},
					(substr($grp, 0, 1) == '*') ? 'ul' : 'ol', $className);
			}
			elseif (strpos($grp, ' ') === 0)
			{
				// preformated text
				$grp = preg_replace(array("/\n /", '/^ /'), array(LF, ''), $grp);
				$grp = self::__wikiParseGrp($grp);
				$this->xhtml .= "<pre$className>$grp</pre>";
			}
			elseif (strpos($grp, '&gt; ') === 0)
			{
				// blockquote
				$grp = preg_replace(array("/\n&gt; /", '/^&gt; /'), array(LF, ''), $grp);
				$grp = self::__wikiParseGrp($grp);
				$this->xhtml .= "<blockquote$className>$grp</blockquote>";
			}
			elseif (strpos($grp, '----') === 0)
			{
				// horizontal rule
				$this->xhtml .= "<hr$className />";
			}
			elseif (strpos($grp, ';; ') !== 0)
			{
				// paragraph (and not comment)
				$grp = self::__wikiParseGrp($grp);
				$this->xhtml .= "<p$className>$grp</p>";
			}
		}
		
		// footnotes
		if (!empty(self::$footnotes))
			$this->xhtml .= '<ul class="footnotes"><li>'.implode('</li><li>', self::$footnotes).'</li></ul>';
	}
	
	protected static function __wikiParseGrp($text)
	{
		// footnotes
		$text = preg_replace_callback('/\(\((.+?)\)\)/', array('self', '__preg_footnotes'), $text);
		
		// support for span
		$text = preg_replace('|&lt;span\s*(.+?)&gt;(.+?)&lt;/span&gt;|', '<span \1>\2</span>', $text);
		
		// media files
		$text = preg_replace_callback('/\{\{(.+?)\}\}/', array('self', '__preg_media'), $text);
		
		// links
		$text = self::__links($text);
		
		// inline syntax
		// FIXME Test then cleanup those REGEXP
		$syntax_src = array (
			// emphasis
			'#(?<!http:)(?<!https:)(?<!ftp:)//(.+?)(?<!http:)(?<!https:)(?<!ftp:)//#',
			/*'#(?!<.*?)//([^<>]+?)//(?![^<>]*?>)#',*/
			// bold
			'/\*\*(.+?)\*\*/',
			/*'/(?!<.*?)\*\*([^<>]+?)\*\*(?![^<>]*?>)/',*/
			// underline
#			'/__(.+?)__/',
			'/(?!<.*?)__([^<>]+?)__(?![^<>]*?>)/',
			// deleted
			'/~~(.+?)~~/',
			/*'/(?!<.*?)~~([^<>]+?)~~(?![^<>]*?>)/',*/
			// exponent
			'/\^\^(.+?)\^\^/',
			/*'/(?!<.*?)^^([^<>]+?)^^(?![^<>]*?>)/',*/
			// line break
			'/%%%/',
			// citation
			'/\?\?([^|]+?)\?\?/',
			'/\?\?(.+?)\|(.+?)\?\?/',
			// comments
			'!/\*[^*]*\*+([^/][^*]*\*+)*/!'
		);
		$syntax_dest = array (
			'<i>\1</i>',
			'<b>\1</b>',
			'<u>\1</u>',
			'<del>\1</del>',
			// exponent
			'<sup>\1</sup>',
			// line break
			'<br />',
			// citation
			'<q>\1</q>',
			'<q cite="\2">\1</q>',
			// comments
			''
		);
		$text = preg_replace(&$syntax_src, &$syntax_dest, $text);
		
		return $text;
	}
	
	function renderToc(&$toc, $h=1)
	{
		$i = 1;
		$str = '';
		while (isset ($toc[$i]))
		{
			$str .= '<li class="h'.$h.'"><a href="#'.$toc[$i]['id'].'">'.$toc[$i]['title'].'</a>';
			if (isset ($toc[$i][1]) and is_array($toc[$i][1]))
			{
				$str .= '<ul class="h'.$h.'">';
				$str .= $this->renderToc($toc[$i], $h +1);
				$str .= '</ul>';
			}
			$str .= '</li>';
			$i++;
		}
		return $str;
	}
	
	// internals
	
	protected function __links($text=null)
	{
		// links
		$from = array(
			// clickable images
			'/(\<img [^\>]* \/\>):([^<>\s\n]*)\b\/?/',
			// wiki syntax links
			'/"([^"]+)[ ]?\(([^)]+)\)":([^<>\s\n]*)\b\/?/',
			'/"([^"]+)":([^<>\s\n]*)\b\/?/',
			// links
			/*'#(?!<.*?)(http|ftp|mailto)+(s)?:(//|)([^<>\s\n]*)\b\/?(?![^<>]*?>)#',*/
			'#(?!<.*?)(http://|https://|ftp://|mailto:)([^<>\s\n]*)\b\/?(?![^<>]*?>)#',
			// external links
#			"#href=\"(http[s]?://(?!".preg_quote(env('HTTP_HOST')).")[^\"]+)\"#",
#			"#href=\"(ftp[s]?://[^\"]+)\"#"
		);
		$to = array(
			// clickable images
			'<a href="\2">\1</a>',
			// wiki syntax links
			'<a href="\3" title="\2">\1</a>',
			'<a href="\2">\1</a>',
			// links
			'<a href="\0">\1\2</a>',
			// external links
#			'class="extern" href="\1"',
#			'class="extern" href="\1"'
		);
		
		if (!is_null($text))
			return preg_replace($from, $to, $text);
		else
			$this->xhtml = preg_replace($from, $to, $this->xhtml);
	}
	
	protected function __list($grp, $char='*', $tag='ul', $className=null)
	{
		$items   = explode(LF, $grp);
		$_index  = 1;
		$started = false;
		
		$str = "<$tag$className>";
		foreach($items as $item)
		{
			preg_match('/('.preg_quote($char).'+)\s*(.+?)$/', $item, $match);
			
			if (empty($match))
				continue;
			
			$index = strlen($match[1]);
			
			// index
			if ($index == $_index)
			{
				$str .= $started ? "</li><li>" : "<li>";
				$started = true;
			}
			elseif ($index > $_index)
			{
				$str .= "<$tag><li>";
			}
			elseif ($index < $_index)
			{
				while($index != $_index)
				{
					$str .= "</li></$tag>";
					$_index--;
				}
				$str .= "</li><li>";
			}
			$_index = $index;
			
			// line (text)
			$str .= $this->__wikiParseGrp($match[2]);;
		}
		
		// closing opened tags
		while($_index != 0)
		{
			$str .= "</li></$tag>";
			$_index--;
		}
		
		return $str;
	}
	
	protected static function __preg_media($match)
	{
		$styles = array();
		
		// extract infos, options, etc.
		if (strpos($match[1], '|') !== false)
		{
			$options = explode('|', $match[1]);
			$url     = array_shift($options);
			
			foreach($options as $opt)
			{
				switch ($opt)
				{
					case 'left':  case 'floatleft':
						 array_push($styles, 'float: left; margin: .5em .5em .5em 0;');
						 break;
					
					case 'right': case 'floatright':
						array_push($styles, 'float: right; margin: .5em 0 .5em .5em;');
						break;
					
					case 'border':
						array_push($styles, 'border: thin solid;');
						break;
					
					default :
						if (preg_match('/^[0-9]+x[0-9]+$/', $opt))
						{
							$opt  = explode('x', $opt);
							$size = ' width="'.$opt[0].'" height="'.$opt[1].'" ';
						}
						elseif (in_array($opt, self::$imageSizes))
						{
							if (!isset($match[2])) $match[2] = $url;
							$url  = "/img.php?url=$url&size=$opt";
						}
						else
						{
							$alt = $opt;
						}
				}
			}
		}
		else
		{
			$url = $match[1];
		}
		
		// media size?
		$size = isset($size) ? $size : null;
		$alt  = isset($alt)  ? $alt  : null;
		list($file, $ext) = f_basename($url);
		
		// media type
		switch (strtolower($ext))
		{
			// vector image
			case 'svg':
			case 'svgz':
				$media = '<object type="image/svg+xml" data="'.$url.'"'.$size.'>' .
					'<embed src="'.$url.'" type="image/svg+xml"'.$size.' />' .
				'</object>';
				break;
			
			// flash object
			case 'swf':
				$media = <<<FlashObject
<object type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" data="$url" $size>
	<param name="movie" value="$url" />
</object>
FlashObject;
				break;
			
			// podcast
			case 'mp3':
				$size = isset($size) ? $size : 'width="200" height="20"';
				$media =<<<MP3Object
<object type="application/x-shockwave-flash" data="/swf/neolao/player_mp3_maxi.swf" $size>
	<param name="wmode" value="transparent" />
	<param name="movie" value="/swf/neolao/player_mp3_maxi.swf" />
	<param name="FlashVars" value="mp3=$url&amp;bgcolor1=ffffff&amp;bgcolor2=ffffff&amp;buttoncolor=000000&amp;buttonovercolor=999999&amp;slidercolor1=333333&amp;slidercolor2=000000&amp;sliderovercolor=999999&amp;textcolor=0&amp;showvolume=1" />
</object>
MP3Object;
				break;
			
			// vidcast
			case 'flv':
				$size = isset($size) ? $size : 'width="320" height="240"';
				$media =<<<FLVObject
<object type="application/x-shockwave-flash" data="/swf/neolao/player_flv_maxi.swf" $size>
	<param name="wmode" value="transparent" />
	<param name="movie" value="/swf/neolao/player_flv_maxi.swf" />
	<param name="FlashVars" value="flv=$url&amp;showtime=1&amp;margin=0&amp;playercolor=ffffff&amp;bgcolor1=ffffff&bgcolor2=ffffff&amp;buttoncolor=333333&amp;buttonovercolor=999999&amp;slidercolor1=333333&amp;slidercolor2=000000&amp;sliderovercolor=999999&amp;loadingcolor=0" />
</object>
FLVObject;
				break;
			
			// Ogg Theora / Ogg Vorbis
			case 'ogg':
				$size = isset($size) ? $size : 'width="320" height="240"';
				if (!isset($id))
					$addJs = true;
				$id   = md5(uniqid());
				$media =<<<OggApplet
<applet code="com.fluendo.player.Cortado.class" archive="/applets/cortado-ovt.jar" $size id="$id">
	<param name="url" value="$url"/>
	<param name="autoPlay" value="false"/>
</applet>
<div class="cortado-buttons">
	<label class="play"  for="$id">play</label>
	<label class="pause" for="$id">pause</label>
	<label class="stop"  for="$id">stop</label>
</div>
OggApplet;
				if (isset($addJs) and $addJs)
				{
					$media .=<<<OggJavascript
<script type="text/javascript">
Window.onDomReady(function()
{
	\$S('.cortado-buttons label').each(function(label)
	{
		label.addEvent('click', function()
		{
			var applet = document.getElementById(label.getAttribute('for'));
			var action = label.getAttribute('class').capitalize();
			applet['do' + action]();
		});
	});	
});
</script>
OggJavascript;
				}
				break;
			
			// image
			default:
				$media = '<img src="'.$url.'" alt="'.$alt.'" style="'.implode(';', $styles).'"'.$size.' />';
		}
		
		return isset($match[2]) ? '<a href="'.$match[2].'">'.$media.'</a>' : $media;
	}
	
	protected static function __preg_footnotes($match)
	{
		static $fn_index = 0;
		$fn_index++;
		
		$html  = self::__wikiParseGrp($match[1]);
		$title = strip_tags($html);
		
		array_push(self::$footnotes, '<a id="fn_'.$fn_index.'" href="#fn_ref_'.$fn_index.'">'.$fn_index.'.</a> '.$html);
		return '<sup><a class="footnote-ref" id="fn_ref_'.$fn_index.'" title="'.$title.'" href="#fn_'.$fn_index.'">'.$fn_index.'</a></sup>';
	}
	
	protected static function __preg_wikilinks($match)
	{
		$link = explode('|', $match[1], 2);
		if (!isset($link[1]))
			$link[1] = $link[0];
		$link[0] = self::url('/site/'.Title::wikiEncode($link[0]));
		
		return '<a href="'.$link[0].'">'.$link[1].'</a>';
	}
}
?>